package verify

import (
	"context"
	"fmt"
	"net"
	"strings"

	"dns-tools/pkg/dns"
	"dns-tools/pkg/dnssec"
	"dns-tools/pkg/validator"

	mdns "github.com/miekg/dns"
)

// 解析器改用统一的 dns.Resolver 接口（Query/QueryCAA/ResolveCNAMEChain/QueryAll）

// CAAVerificationResult CAA验证结果
type CAAVerificationResult struct {
	Domain      string          `json:"domain"`
	Brand       string          `json:"brand"`
	Valid       bool            `json:"valid"`
	Message     string          `json:"message"`
	CAARecords  []dns.CAARecord `json:"caa_records,omitempty"`
	CNAMEChain  []string        `json:"cname_chain,omitempty"`
	RootDomain  string          `json:"root_domain,omitempty"`
	RootChecked bool            `json:"root_checked"`
	// Solution 字段已移除，不再返回任何解决方案
	Errors []string        `json:"errors,omitempty"` // 额外错误信息
	DNSSEC *dns.DNSSECInfo `json:"dnssec,omitempty"` // DNSSEC 检测信息
}

// CAASolution CAA问题解决方案
type CAASolution struct {
	Description string              `json:"description"`
	Options     []CAASolutionOption `json:"options"`
}

// CAASolutionOption 解决方案选项
type CAASolutionOption struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	Example     string `json:"example,omitempty"`
}

// CAAVerifier CAA验证器
type CAAVerifier struct {
	resolver       dns.Resolver
	checkRoot      bool
	brandMappings  map[string][]string
	dnssecDetector *dnssec.Detector
}

// NewCAAVerifier 创建CAA验证器
func NewCAAVerifier(resolver dns.Resolver) *CAAVerifier {
	if resolver == nil {
		// Use default DNS resolver
		resolver = dns.NewResolver([]string{"8.8.8.8", "8.8.4.4"})
	}

	return &CAAVerifier{
		resolver:       resolver,
		checkRoot:      true, // 默认检查根域名
		dnssecDetector: dnssec.NewDetector(), // 初始化 DNSSEC 检测器
		brandMappings: map[string][]string{
			// 严格匹配原工具的 CAA 值
			"certum":     {"certum.pl", "certum.eu"},
			"sectigo":    {"sectigo.com", "comodoca.com", "comodo.com"}, // Sectigo 前身为 Comodo CA
			"digicert":   {"digicert.com"},
			"globalsign": {"globalsign.com"},
			"sheca":      {"sheca.com"},
		},
	}
}

// EnableRootCheck 启用/禁用根域名检查
func (v *CAAVerifier) EnableRootCheck(enable bool) {
	v.checkRoot = enable
}

// Verify 验证域名的CAA记录
func (v *CAAVerifier) Verify(ctx context.Context, domain, brand string) *CAAVerificationResult {
	result := &CAAVerificationResult{
		Domain: domain,
		Brand:  brand,
		Valid:  true,
	}

	// 检查是否为 IP 地址：IP 地址无需 CAA 验证，直接通过
	cleanDomain := strings.TrimPrefix(domain, "*.")
	if net.ParseIP(cleanDomain) != nil {
		result.Valid = true
		result.Message = "IP 地址无需 CAA 验证"
		return result
	}

	// 验证域名格式
	if err := validator.ValidateDomain(domain); err != nil {
		result.Valid = false
		result.Message = fmt.Sprintf("域名格式无效: %v", err)
		return result
	}

	// 标准化域名和品牌
	domain = validator.NormalizeDomain(domain)
	brand = strings.ToLower(strings.TrimSpace(brand))

	// 对通配域名：查询解析时去掉前缀"*."，但验证逻辑按通配处理，前端展示保留原值
	isWildcard := strings.HasPrefix(domain, "*.")
	lookupDomain := domain
	if isWildcard {
		lookupDomain = strings.TrimPrefix(domain, "*.")
	}

	// 解析CNAME链（增强错误处理）
	// 注意：对于通配符，使用去掉 "*." 后的 lookupDomain 进行查询
	cnameChain, err := v.resolveCNAMEChainWithErrors(ctx, lookupDomain)
	if err != nil {
		// 处理 CNAME 链错误
		if strings.Contains(err.Error(), "circular") {
			result.Valid = false
			result.Message = "CNAME 链存在循环引用"
			return result
		} else if strings.Contains(err.Error(), "too long") {
			result.Valid = false
			result.Message = "CNAME 链过长（超过10层）"
			return result
		}
		// 其他错误继续处理
		// 对于通配符，CNAMEChain 应该存储去掉 "*." 后的域名（用于后续 DNS 查询）
		result.CNAMEChain = []string{lookupDomain}
	} else if len(cnameChain) > 0 {
		result.CNAMEChain = cnameChain
	} else {
		// 没有 CNAME：存储去掉 "*." 后的域名
		result.CNAMEChain = []string{lookupDomain}
	}

	// 检查CNAME链中每个域名的CAA记录
	var allCAA []dns.CAARecord
	hasCAA := false

	for _, checkDomain := range result.CNAMEChain {
		records, err := v.resolver.QueryCAA(ctx, checkDomain)
		if err != nil {
			continue // 忽略查询错误
		}

		if len(records) > 0 {
			hasCAA = true
			allCAA = append(allCAA, records...)
			// 找到CAA记录就停止（第一个有CAA的域名生效）
			break
		}
	}

	// 新规则：
	// - 对于普通域名（非通配符）：始终检查"被检测域名"的根域（无论是否存在 CNAME 链）
	// - 对于通配符域名：
	//   - 根域名通配符（*.example.com）：已经在上面循环中查询了 example.com，不需要再查询根域
	//   - 子域名通配符（*.sub.example.com）：需要检查根域 example.com（如果 sub.example.com 没有 CAA）
	// - 不检查 CNAME 链目标域名的根域
	// 例如：abc.cde.com CNAME 到 target.other.com
	//   - 检查顺序：abc.cde.com -> cde.com（被检测域名的根域）-> target.other.com
	//   - 不检查：other.com（CNAME 链目标域名的根域）
	if !hasCAA && v.checkRoot {
		// 使用被检测域名（CNAME 链的第一个元素）来提取根域
		// 注意：对于通配符，CNAMEChain[0] 已经是去掉 "*." 后的域名
		firstDomain := result.CNAMEChain[0]
		rootDomain, err := validator.ExtractRootDomain(firstDomain)

		// 避免重复查询：如果第一个域名已经是根域，则跳过
		// 这种情况包括：根域名本身（example.com）或根域名通配符（*.example.com，CNAMEChain[0] = example.com）
		if err == nil && rootDomain != "" && rootDomain != firstDomain {
			rootRecords, err := v.resolver.QueryCAA(ctx, rootDomain)
			if err == nil && len(rootRecords) > 0 {
				hasCAA = true
				allCAA = append(allCAA, rootRecords...)
				result.RootDomain = rootDomain
				result.RootChecked = true
			}
		}
	}

	result.CAARecords = allCAA

	// 验证CAA记录
	if !hasCAA {
		result.Valid = true
		result.Message = "未发现CAA记录，允许所有CA颁发证书"
	} else {
		// 检查是否有允许指定品牌的CAA记录
		// 规则：
		// - 标准域名：仅检查 tag=issue
		// - 通配符域名：如果 issue 与 issuewild 存在，则两者都需要匹配品牌；若仅存在其中之一，则检查存在的那一类。
		hasAnyIssueTag := false
		if !isWildcard {
			// 标准域名：仅 issue
			brandAllowed := false
			for _, caa := range allCAA {
				if caa.Tag == "issue" {
					hasAnyIssueTag = true
					if v.matchBrand(caa.Value, brand) {
						brandAllowed = true
						break
					}
				}
			}
			if !hasAnyIssueTag {
				result.Valid = true
				result.Message = "CAA记录不包含颁发限制"
			} else if brandAllowed {
				result.Valid = true
				result.Message = fmt.Sprintf("CAA记录允许%[1]s颁发证书", brand)
			} else {
				result.Valid = false
				result.Message = fmt.Sprintf("CAA记录存在但不允许%[1]s颁发证书", brand)
			}
		} else {
			// 通配域名：先检查 issuewild；存在则以 issuewild 判定；不存在再检查 issue
			issuewildPresent := false
			issuewildAllowed := false
			issuePresent := false
			issueAllowed := false
			for _, caa := range allCAA {
				switch caa.Tag {
				case "issuewild":
					issuewildPresent = true
					if v.matchBrand(caa.Value, brand) {
						issuewildAllowed = true
					}
				case "issue":
					issuePresent = true
					if v.matchBrand(caa.Value, brand) {
						issueAllowed = true
					}
				}
			}

			if issuewildPresent {
				if issuewildAllowed {
					result.Valid = true
					result.Message = fmt.Sprintf("CAA issuewild 允许%[1]s颁发通配证书", brand)
				} else {
					result.Valid = false
					result.Message = fmt.Sprintf("CAA issuewild 未允许%[1]s", brand)
				}
			} else {
				// 不存在 issuewild，则回落检查 issue
				if !issuePresent {
					result.Valid = true
					result.Message = "CAA记录不包含颁发限制"
				} else if issueAllowed {
					result.Valid = true
					result.Message = fmt.Sprintf("CAA issue 允许%[1]s颁发通配证书", brand)
				} else {
					result.Valid = false
					result.Message = fmt.Sprintf("CAA issue 未允许%[1]s", brand)
				}
			}
		}
	}

	// DNSSEC 检测（非阻塞，仅提示）
	if v.dnssecDetector != nil {
		// 使用去掉通配符前缀的域名进行 DNSSEC 检测
		dnssecInfo := v.dnssecDetector.DetectState(ctx, lookupDomain, mdns.TypeCAA)
		result.DNSSEC = dnssecInfo

		// 如果有风险，在 Message 中添加提示（不改变 Valid 状态）
		if dnssecInfo != nil && dnssecInfo.RiskLevel != dns.DNSSECRiskNone {
			dnssecMsg := dnssec.FormatDNSSECMessage(dnssecInfo)
			if dnssecMsg != "" {
				result.Message += dnssecMsg
			}
		}
	}

	return result
}

// resolveCNAMEChainWithErrors 解析 CNAME 链并返回详细错误
func (v *CAAVerifier) resolveCNAMEChainWithErrors(ctx context.Context, domain string) ([]string, error) {
	chain := []string{domain}
	seen := make(map[string]bool)
	seen[domain] = true

	currentDomain := domain
	maxDepth := 10 // 最大深度限制

	for i := 0; i < maxDepth; i++ {
		// 使用 resolver 查询 CNAME
		if dnsResolver, ok := v.resolver.(*dns.LocalResolver); ok {
			records, err := dnsResolver.Query(ctx, currentDomain, dns.TypeCNAME)
			if err != nil || len(records) == 0 {
				// 没有 CNAME 记录，结束
				break
			}

			cname := records[0].Value
			cname = strings.TrimSuffix(cname, ".")

			// 检查循环
			if seen[cname] {
				return chain, fmt.Errorf("circular CNAME reference detected: %s -> %s", currentDomain, cname)
			}

			// 检查是否到达终点
			if cname == currentDomain {
				break
			}

			chain = append(chain, cname)
			seen[cname] = true
			currentDomain = cname
		} else {
			// 使用通用接口
			cnameChain, err := v.resolver.ResolveCNAMEChain(ctx, domain)
			if err != nil {
				return nil, err
			}
			return cnameChain, nil
		}
	}

	// 检查链长度
	if len(chain) >= maxDepth {
		return chain, fmt.Errorf("CNAME chain too long: exceeds %d levels", maxDepth)
	}

	return chain, nil
}

// matchBrand 检查CAA值是否匹配品牌
func (v *CAAVerifier) matchBrand(caaValue, brand string) bool {
	caaValue = strings.ToLower(strings.TrimSpace(caaValue))
	brand = strings.ToLower(strings.TrimSpace(brand))

	// 获取品牌的所有可能值
	if possibleValues, exists := v.brandMappings[brand]; exists {
		for _, value := range possibleValues {
			if strings.Contains(caaValue, value) {
				return true
			}
		}
		return false // 如果有映射，只使用映射的值
	}

	// 没有映射时才尝试直接匹配完整品牌名
	// 精确匹配或匹配 brand.com 格式，但要避免部分匹配
	if caaValue == brand {
		return true
	}
	brandDotCom := brand + ".com"
	return caaValue == brandDotCom
}

// GenerateSolution 生成解决方案（为了兼容测试代码）
func (v *CAAVerifier) GenerateSolution(result *CAAVerificationResult) *CAASolution {
	if result.Valid {
		return nil
	}

	solution := &CAASolution{
		Description: "CAA记录验证失败",
		Options: []CAASolutionOption{
			{
				Title:       "删除现有CAA记录",
				Description: "删除域名的CAA记录以允许所有CA颁发证书",
			},
			{
				Title:       "添加允许记录",
				Description: "添加允许指定品牌的CAA记录",
				Example:     fmt.Sprintf("example.com. CAA 0 issue \"%s.com\"", result.Brand),
			},
		},
	}

	return solution
}

// VerifyMultiple 批量验证多个域名
func (v *CAAVerifier) VerifyMultiple(ctx context.Context, domains []string, brand string) []*CAAVerificationResult {
	results := make([]*CAAVerificationResult, len(domains))

	for i, domain := range domains {
		results[i] = v.Verify(ctx, domain, brand)
	}

	return results
}
